﻿#region License Info
//Component of Cronos Package, http://www.codeplex.com/cronos
//Copyright (C) 2009 Anthony Brockwell

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#endregion


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using MathNet.Numerics.LinearAlgebra;

namespace ExtraControlLibrary
{
    public partial class ShadedSurfaceBox : UserControl
    {
        private SurfaceEstimate surface;
        public SurfaceEstimate Surface 
        {
            get
            {
                return surface;
            }
            set
            {
                if (value == null)
                {
                    surface = null;
                    return;
                }
                if (value.Dimension != 2)
                    throw new ApplicationException("Surface must be defined on a two-dimensional domain.");
                surface = value;
                UpdatePanels();
            }
        }

        public int ResolutionPerDimension { get; set; }


        public double MinValue { get; set; }
        public double MaxValue { get; set; }

        public double MinValueForColor { get; set; }
        public double MaxValueForColor { get; set; }

        private TimeSpan[] niceTimes =
            {
                new TimeSpan(0,0,0,20),
                new TimeSpan(0, 1, 0),
                new TimeSpan(0, 30, 0),
                new TimeSpan(1, 0, 0),
                new TimeSpan(3, 0, 0),
                new TimeSpan(6, 0, 0),
                new TimeSpan(12, 0, 0),
                new TimeSpan(24, 0, 0),
                new TimeSpan(48, 0, 0), 
                new TimeSpan(7, 0, 0, 0),
                new TimeSpan(30, 0, 0, 0),
            };

        protected class Panel
        {
            public double x0, x1, y0, y1;
            public Vector midPoint;

            public double CachedValue;

            public Rectangle ComputePixels(SurfaceEstimate surface, Rectangle bigRectangle)
            {
                double newx0 = (x0 - surface.LowerBound[0])/ (surface.UpperBound[0] - surface.LowerBound[0]);
                double newx1 = (x1 - surface.LowerBound[0]) / (surface.UpperBound[0] - surface.LowerBound[0]);
                newx0 = newx0*bigRectangle.Width + bigRectangle.Left;
                newx1 = newx1*bigRectangle.Width + bigRectangle.Left;
                double newy0 = (y0 - surface.LowerBound[1])/(surface.UpperBound[1] - surface.LowerBound[1]);
                double newy1 = (y1 - surface.LowerBound[1]) / (surface.UpperBound[1] - surface.LowerBound[1]);
                newy0 = (bigRectangle.Height) * (1-newy0);
                newy1 = (bigRectangle.Height) * (1 - newy1);
                int localht = (int)(newy0 - newy1 + 1);
                return new Rectangle((int) newx0, (int) newy0 - localht + 1, (int) (newx1-newx0+1), localht);
            }
        }

        private List<Panel> panels;

        public ShadedSurfaceBox()
        {
            MinValueForColor = -0.4;
            MaxValueForColor = 0.4;
            ResolutionPerDimension = 28;
            InitializeComponent();
            surface = null;
        }

        public void Recompute()
        {
            UpdatePanels();
            Refresh();
        }

        private void UpdatePanels()
        {
            // construct panels to cover the domain of surface
            Vector x0 = surface.LowerBound;
            Vector x1 = surface.UpperBound;

            panels = new List<Panel>(256);
            int divs = ResolutionPerDimension;
            for (int i=0 ; i<divs ; ++i)
                for (int j=0 ; j<divs ; ++j)
                {
                    Panel p = new Panel();
                    p.x0 = j*(x1[0] - x0[0])/divs + x0[0];
                    p.x1 = (j+1)*(x1[0] - x0[0])/divs + x0[0];
                    p.y0 = i * (x1[1] - x0[1]) / divs + x0[1];
                    p.y1 = (i+1) * (x1[1] - x0[1]) / divs + x0[1];
                    p.midPoint = new Vector(new[] {(p.x0 + p.x1)/2, (p.y0 + p.y1)/2});
                    p.CachedValue = surface.GetValueAt(p.midPoint);
                    panels.Add(p);

                    if (panels.Count == 1)
                    {
                        MinValue = p.CachedValue;
                        MaxValue = p.CachedValue;
                    }
                    else
                    {
                        if (p.CachedValue > MaxValue)
                            MaxValue = p.CachedValue;
                        if (p.CachedValue < MinValue)
                            MinValue = p.CachedValue;
                    }
                }
        }

        public Brush GetBrushForValue(double value)
        {
            // value should be between -1 and 1
            double grayness = (value - MinValueForColor)/(MaxValueForColor - MinValueForColor);
            if (grayness > 1)
                grayness = 1;
            if (grayness < 0)
                grayness = 0;
            int pixval = (int) Math.Round(255*grayness);
            int red = 255 - pixval;
            int green = pixval;
            int blue = 128;
            var retval = new SolidBrush(Color.FromArgb(red, green, blue));
            return retval;
        }

        protected void DrawXLabels(Graphics g, Rectangle labelArea)
        {
            int depth = 6;

            Pen fatPen = new Pen(Color.Black, 2);

            // put markers at niceTimes
            foreach (TimeSpan ts in niceTimes)
            {
                double xval = Math.Log(ts.Ticks);
                if (xval >= surface.LowerBound[0] && xval <= surface.UpperBound[0])
                {
                    // we should draw it
                    double frac = (xval - surface.LowerBound[0])/(surface.UpperBound[0] - surface.LowerBound[0]);
                    int xpix = (int) (frac*labelArea.Width) + labelArea.Left;

                    g.DrawLine(fatPen, xpix, labelArea.Top, xpix, labelArea.Top+depth);
                    string lbl = ts.ToString();
                    Size preferredSize =g.MeasureString(lbl, Font).ToSize();
                    g.DrawString(lbl, Font, Brushes.Black, xpix-preferredSize.Width/2, labelArea.Top + depth + 4);

                    if (depth == 6)
                        depth = 25;
                    else
                        depth = 6;
                }
            }

            fatPen.Dispose();
        }

        protected void DrawYLabels(Graphics g, Rectangle labelArea)
        {
            int depth = 6;

            Pen fatPen = new Pen(Color.Black, 2);

            // put markers at niceTimes
            foreach (TimeSpan ts in niceTimes)
            {
                double yval = Math.Log(ts.Ticks);
                if (yval >= surface.LowerBound[1] && yval <= surface.UpperBound[1])
                {
                    // we should draw it
                    double frac = (yval - surface.LowerBound[1]) / (surface.UpperBound[1] - surface.LowerBound[1]);
                    int ypix = labelArea.Bottom - (int)(frac * labelArea.Height);

                    g.DrawLine(fatPen, labelArea.Right - depth, ypix, labelArea.Right, ypix);
                    string lbl = ts.ToString();
                    Size preferredSize = g.MeasureString(lbl, Font).ToSize();
                    g.DrawString(lbl, Font, Brushes.Black, labelArea.Right - depth - 5 - preferredSize.Width,labelArea.Top + ypix - preferredSize.Height/2);
                }
            }

            fatPen.Dispose();
        }


        protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);

            var g = pe.Graphics;

            // partition into main space, xlabels, ylabel, legend
            int leftLine = (int) (0.18*Width);
            int bottomLine = (int) (0.8*Height);
            int rightMargin = (int) (0.18*Width);
            var mainRect = new Rectangle(leftLine, 0, Width - leftLine - 1 - rightMargin, bottomLine);
            var xlabelRect = new Rectangle(leftLine, bottomLine, Width - leftLine - 1 - rightMargin, Height - bottomLine - 1);
            var ylabelRect = new Rectangle(0, 0, leftLine, bottomLine);
            var legendRect = new Rectangle(Width - rightMargin - 1, 0, rightMargin - 1, bottomLine);

            //g.DrawRectangle(Pens.Black, mainRect);
            //g.DrawRectangle(Pens.Black, xlabelRect);
            //g.DrawRectangle(Pens.Black, ylabelRect);
            //g.DrawRectangle(Pens.Black, legendRect);

            if (surface == null)
                return;

            // now draw the panels
            foreach (Panel p in panels)
            {
                Rectangle r = p.ComputePixels(surface, mainRect);
                double value = p.CachedValue;
                Brush brush = GetBrushForValue(value);
                g.FillRectangle(brush, r);
                brush.Dispose();
            }

            // then draw the labels
            DrawXLabels(g, xlabelRect);
            DrawYLabels(g, ylabelRect);

            if (Legend != null)
                DrawLegend(g, legendRect);
        }


        public Dictionary<Brush, double> Legend { get; set; }

        protected void DrawLegend(Graphics g, Rectangle legendArea)
        {
            if (Legend == null)
                return;

            int pixelRow = 10;
            foreach (var x in Legend)
            {
                string label = String.Format("{0:0.0000}", x.Value);

                Rectangle splash = new Rectangle(6 + legendArea.Left, pixelRow, 40, 20);

                g.FillRectangle(x.Key, splash);
                g.DrawRectangle(Pens.Black, splash); // put a border there

                // Draw left justified text
                Rectangle rect = new Rectangle(48 + legendArea.Left, pixelRow + 3, 60, 20);
                g.DrawString(label, Font, Brushes.Black, rect);

                pixelRow += 28;
            }
        }
    }
}
